Repository navigation
Phase 9: zero axe violations on login, dashboard and providers at 375-1440px, keyboard flows - #32
Merged
Merged
Conversation
Extract Modal's Escape handler, initial focus, Tab trap and focus restore into src/shared/hooks/useDialogFocus.ts so slide-over drawers can reuse the exact same contract. The trap now skips disabled controls: a disabled last button refused focus and let Tab escape the dialog. Tests: tests/unit/shared/components/Modal.a11y.test.tsx (dialog name, aria-modal, named close button, initial focus, Tab wrap skipping disabled, Escape); tests/unit/ui/modal-focus-restoration.test.tsx unchanged and green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Checkbox generates an id when none is passed so the visible label keeps an explicit htmlFor association. Toggle links its visible description through aria-describedby when a label or ariaLabel already names the switch (no double announcement when the description is the name). Tests: tests/unit/shared/components/Checkbox.a11y.test.tsx and tests/unit/shared/components/Toggle.a11y.test.tsx (role=switch, aria-checked, Space/Enter, description, disabled). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
axe serious violations on /dashboard and /dashboard/providers: - nested-interactive: section headers were div[role=button] wrapping the pin button and could not be reached from the keyboard. SidebarSectionHeader renders the expand control and the pin control as sibling buttons (aria-expanded, aria-pressed), same visual layout. - aria-hidden-focus: the collapse button lived inside an aria-hidden container; only the decorative window dots are hidden now. Collapsed (icon-only) nav links get aria-label, restart/shutdown get explicit names, and Material Symbols glyphs are aria-hidden so ligature text is not read. Tests: tests/unit/shared/components/SidebarSectionHeader.a11y.test.tsx and tests/unit/shared/components/Sidebar.a11y.test.tsx. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ProviderTestSlideOver: aria-modal, focus moves in, Tab is trapped, Escape closes and focus returns to the opener (useDialogFocus); model/key selects are labelled; the logs loading state is role=status and a load failure is role=alert; glyphs are aria-hidden. DashboardLayout mobile drawer: inert while closed (its links stayed in the tab order and accessibility tree while translated off-canvas), role=dialog + aria-modal + name while open, same focus contract, aria-hidden backdrop. Tests: tests/unit/shared/components/ProviderTestSlideOver.a11y.test.tsx and tests/unit/shared/components/DashboardLayout.drawer.a11y.test.tsx. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The visible "Password" label is tied to the input (htmlFor/id), the initial loading state is a polite status region, a failed sign-in is role=alert, and decorative Material Symbols glyphs are aria-hidden. No behavior change. Tests: tests/unit/ui/login-page-a11y.test.tsx. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…links Breadcrumbs read --text-primary/--text-secondary, which are not defined, so the dark-theme fallbacks (#e0e0e0, diegosouzapw#888) rendered in light mode (axe color-contrast 1.25:1 on the current crumb). They now use --color-text-main/--color-text-muted /--color-accent. Home inline links in body copy are always underlined (axe link-in-text-block: brand red vs muted grey text is 1.27:1, so color alone does not identify them). HomePageClient.tsx line count unchanged (frozen file). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
After the structural fixes, axe color-contrast (serious) was the only remaining violation on /dashboard and /dashboard/providers at every width. Fixed at the token level where possible, measured against the backgrounds axe reported: - --color-text-muted #71717a -> #65656e (light only): was 4.25:1 on bg-subtle, 4.44:1 on the sidebar and 3.81:1 on control grays; now >= 4.55:1 on all. - --color-primary #e54d5e -> #b83242 and hover #c93d4e -> #9f2a38 (light only): primary text on its /10 tint was 3.07:1 and white on primary 3.78:1; now 4.68:1 and 5.88:1. .dark pins the original coral, which passes there (5.11:1) while the darker shade would not (3.29:1). - Muted text with opacity modifiers (/40, /60, /70, opacity-70) cannot reach 4.5:1 at any shade: sidebar section titles, item subtitles, group separators, no-results line, sponsor banner notes and provider category counts now use the full token. Decorative aria-hidden glyph opacities are unchanged. - Sidebar restart/shutdown labels: amber-500/red-500 (1.96:1 / 3.5:1) -> amber-700/red-700 in light theme, 500 shades kept in dark. - Kimi sponsor CTA: white on #1783FF was 3.67:1 -> #0b6ad4 (5.22:1). Partner accent colors elsewhere (card borders, KIMI_BRAND_COLOR) are untouched. - Provider category pills expose aria-pressed like the media chips; the media chip glyph is aria-hidden. Visual change: light-theme primary and muted text are visibly darker. Dark theme is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…kens themeStore wrote the preset color inline on <html> for every preset, including the default "coral". An inline custom property beats both :root and .dark, so the contrast-safe light-theme primary from globals.css never applied: axe still measured #e54d5e (white on primary 3.78:1, primary text on its tint 3.07:1). The default preset, and unknown ids that already fell back to it, now remove the inline --color-primary/--color-primary-hover so the CSS tokens apply (deeper coral in light, original coral in dark). Other presets and custom colors are user choices and are still applied inline. COLOR_THEMES.coral is unchanged (Appearance swatch). Tests: tests/unit/ui/theme-store-default-primary.test.tsx (6); existing tests/unit/ui/providerIconKimiLogomark.test.tsx still green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rd flows - tests/e2e/responsiveSpecs.ts: viewports 375/768/900/1024/1280/1440 (900 and 1024 cover the band below the lg sidebar breakpoint, audit C-09). The overflow check returns the offending elements: the dashboard shell clips horizontal overflow, so body.scrollWidth alone could never fail there. - tests/e2e/responsive.spec.ts: runs authenticated pages too (they were all skipped) and tolerates the /dashboard -> /home client redirect. - tests/e2e/a11y.spec.ts: /login, /dashboard and /dashboard/providers must have zero critical/serious violations at every width from 768 to 1440; the total-count ratchet at 1280px is lowered to the measured values (1/4/3 -> 0/0/0). /dashboard/settings keeps 5 (not re-measured). No axe rules disabled. Types come from @axe-core/playwright via a type-only import (no casts). - tests/e2e/keyboard-a11y.spec.ts (new): keyboard-only sign-in with a visible focus ring, skip link as the first tab stop, sidebar section headers toggled with Enter/Space. Dashboard tests open /home directly: /dashboard redirects there client-side and the redirect raced the shared auth helper's page.evaluate. - tests/unit/batch-b-final.test.ts: the responsiveSpecs matrix assertions follow the new viewport list. Measured locally against an isolated dev server (webpack, temp DATA_DIR/HOME, port 20931) with @axe-core/playwright, wcag2a/2aa/21a/21aa: before 2-4 serious violations per dashboard page (nested-interactive, aria-hidden-focus, color-contrast, link-in-text-block); after 0 on all three pages at all six widths. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
balance-e2e-shards.test.ts requires every e2e spec to have a weight in config/quality/e2e-timings.json. keyboard-a11y.spec.ts (added in this branch) had none, which failed the unit fast-path on PR #32. Weighted by its line count, as the seed documents for new specs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings in PR #35 (v3.8.54 cycle and the /api/metrics governance fix that turned the base gate red, failing Fast Quality Gates and the governance unit test on this PR). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 9 — accessibility and responsiveness
axe (wcag2a/2aa/21a/21aa) now finds 0 violations of any impact on
/login,/dashboardand/dashboard/providersat 375, 768, 900, 1024, 1280 and 1440 px (before: /dashboard 2–4 serious, /dashboard/providers 2–3 serious per width).Changes
useDialogFocusshared hook (Escape, initial focus, Tab trap that skips disabled controls, focus restore) used by Modal, ProviderTestSlideOver and the mobile drawer in DashboardLayout (nowinertwhen closed, namedrole=dialog+aria-modalwhen open).aria-describedby.SidebarSectionHeader: expand and pin are sibling buttons (was adiv[role=button]wrapping a button — axe nested-interactive, not keyboard reachable); collapse control no longer insidearia-hidden; icon-only links named; glyphsaria-hidden.--text-primary/--text-secondarytokens (1.25:1 contrast in light mode); now real theme tokens. Inline body links underlined.--color-text-muted#71717a → #65656e,--color-primary#e54d5e → #b83242 (dark theme keeps #e54d5e), muted text at reduced opacity uses the full token. The theme store no longer writes the default preset inline over the token.a11y.spec.tsrequires zero critical/serious at 768–1440 (baselines lowered to 0, no rules disabled); newkeyboard-a11y.spec.ts(keyboard sign-in with visible focus, skip link first, sidebar sections with Enter/Space).Needs design sign-off (visible)
Darker light-theme primary and muted text; Kimi sponsor button #1783FF → #0b6ad4.
Verification (local)
Vitest 17 files / 91 tests;
batch-b-final.test.ts33/33; ESLint exit 0; complexity no new violations; Playwright responsive + keyboard specs pass against an isolated webpack dev server. Not covered: /dashboard/settings and other pages, dark-theme contrast.🤖 Generated with Claude Code